﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Data;
using System.Collections;

using LorraineSec.DatabaseLib.DataObjects;

namespace LorraineSec.DatabaseLib
{
    /// <summary>
    /// Represents an Xml "database".
    /// </summary>
    public class XmlDatabase : ILibrary
    {
        private String xmlDatabaseFilename = "";
        private XmlDocument xmlDatabase = null;
        private bool isFileLoaded = false;
        private bool isValidated = false;

        #region Properties
        #region Constants
        // primary key
        public const String PKEY = "pKey";
        // items
        public const String DEVICE = "device";
        public const String USER = "user";
        public const String USERGROUP = "usergroup";
        public const String COMPARTMENT = "compartment";
        public const String ASSET = "asset";
        public const String EVENT = "event";

        // attributes
        public const String ID = "id";
        public const String USERNAME = "username";
        public const String PASSWORD = "password";
        public const String RFID = "rfid";
        public const String FNAME = "firstName";
        public const String LNAME = "lastName";
        public const String ACTIVE = "active";
        public const String NAME = "name";
        public const String TYPE = "type";
        public const String TIME = "time";
        public const String CONTENT = "content";
        public const String PASSCODE = "passcode";
        public const String SERIAL = "serial";
        public const String LIBID = "libId";

        // foreign keys
        public const String USERGROUPID = "usergroupId";
        public const String COMPID = "compartmentID";
        public const String USERID = "userId";
        public const String ASSETID = "assetId";
        public const String DEVICEID = "deviceId";
        #endregion

        [Flags]
        private enum TagCheck : int
        {
            hasRootTag = 1,
            hasUsersTag = 2,
            hasUsergroupsTag = 4,
            hasDevicesTag = 8,
            hasCompartmentsTag = 16,
            hasAssetsTag = 32,
            hasLogsTag = 64,
            hasUgToCompTag = 128,
            hasSettingsTag = 256,
            hasAllTags = hasRootTag | hasUsersTag | hasUsergroupsTag | hasDevicesTag | hasCompartmentsTag | hasAssetsTag | hasLogsTag | hasUgToCompTag | hasSettingsTag
        }

        #region Elements
        /// <summary>
        /// Gets the XmlElement whose child nodes are the user elements.
        /// </summary>
        public XmlElement users
        {
            get
            {
                if (isFileLoaded)
                {
                    XmlNode uNode = xmlDatabase.SelectSingleNode("/root/users");
                    return (XmlElement)uNode ?? null;
                }
                return null;
            }
        }

        /// <summary>
        /// Gets the XmlElement whose child nodes are the usergroup elements. 
        /// </summary>
        public XmlElement usergroups
        {
            get
            {
                if (isFileLoaded)
                {
                    XmlNode ugNode = xmlDatabase.SelectSingleNode("/root/usergroups");
                    return (XmlElement)ugNode ?? null;
                }
                return null;
            }
        }

        /// <summary>
        /// Gets the XmlElement whose child nodes are the device elements. 
        /// </summary>
        public XmlElement devices
        {
            get
            {
                if (isFileLoaded)
                {
                    XmlNode dNode = xmlDatabase.SelectSingleNode("/root/devices");
                    return (XmlElement)dNode ?? null;
                }
                return null;
            }
        }

        /// <summary>
        /// Gets the XmlElement whose child nodes are the compartment elements. 
        /// </summary>
        public XmlElement compartments
        {
            get
            {
                if (isFileLoaded)
                {
                    XmlNode lNode = xmlDatabase.SelectSingleNode("/root/compartments");
                    return (XmlElement)lNode ?? null;
                }
                return null;
            }
        }

        /// <summary>
        /// Gets the XmlElement whose child nodes are the asset elements. 
        /// </summary>
        public XmlElement assets
        {
            get
            {
                if (isFileLoaded)
                {
                    XmlNode aNode = xmlDatabase.SelectSingleNode("/root/assets");
                    return (XmlElement)aNode ?? null;
                }
                return null;
            }
        }

        /// <summary>
        /// Gets the XmlElement whose child nodes are the log elements. 
        /// </summary>
        public XmlElement logs
        {
            get
            {
                if (isFileLoaded)
                {
                    XmlNode lNode = xmlDatabase.SelectSingleNode("/root/logs");
                    return (XmlElement)lNode ?? null;
                }
                return null;
            }
        }

        public XmlElement ugToComp
        {
            get
            {
                if (isFileLoaded)
                {
                    XmlNode rNode = xmlDatabase.SelectSingleNode("/root/ugToComp");
                    return (XmlElement)rNode ?? null;
                }
                return null;
            }
        }

        /// <summary>
        /// Gets the XmlElement whose child nodes are the settings elements.  
        /// </summary>
        public XmlElement settings
        {
            get
            {
                if (isFileLoaded)
                {
                    XmlNode sNode = xmlDatabase.SelectSingleNode("/root/settings");
                    return (XmlElement)sNode ?? null;
                }
                return null;
            }
        }
        #endregion
        #endregion

        #region General Functions
        /// <summary>
        /// Empty constructor. 
        /// </summary>
        public XmlDatabase()
        {
        }

        /// <summary>
        /// Does nothing, just a sandbox for testing. 
        /// </summary>
        public void foobar()
        {
            
        }

        /// <summary>
        /// Create a new XmlDatabase object using the passed in filename as the source data.
        /// </summary>
        /// <param name="filename">source file</param>
        public XmlDatabase(String filename)
        {
            if (!File.Exists(filename))
            {
                createNew(filename);
            }

            xmlDatabaseFilename = filename;

            // load and validate
            load(filename);
        }

        /// <summary>
        /// Creates and returns a new XmlDocument formatted for LorraineSec to use as the xml database.
        /// </summary>
        /// <returns>A new XmlDocument formatted for LorraineSec.</returns>
        public XmlDocument createNew(String filename)
        {
            XmlDocument brandNew = new XmlDocument();
            brandNew.InnerXml = "<root></root>";
            XmlElement root = brandNew.DocumentElement;

            root.InnerXml += "<users><pKey>1</pKey></users>";
            root.InnerXml += "<usergroups><pKey>1</pKey></usergroups>";
            root.InnerXml += "<devices><pKey>1</pKey></devices>";
            root.InnerXml += "<compartments><pKey>1</pKey></compartments>";
            root.InnerXml += "<assets><pKey>1</pKey></assets>";
            root.InnerXml += "<logs><pKey>1</pKey></logs>";
            root.InnerXml += "<ugToComp><pKey>1</pKey></ugToComp>";
            root.InnerXml += "<settings></settings>";

            brandNew.Save(filename);
            this.loadNew(filename);
            return xmlDatabase;
        }

        /// <summary>
        /// Loads a new .xml file from the passed in filename, replacing any previously loaded file. 
        /// </summary>
        /// <param name="filename">The .xml filename to load.</param>
        public void loadNew(String filename)
        {
            unload();
            load(filename);
        }

        /// <summary>
        /// Opens the .xml file at the passed filename. 
        /// 
        /// Throws an exception if a file is already loaded.
        /// </summary>
        /// <param name="filename">The .xml filename to load.</param>
        public void load(String filename)
        {
            if (xmlDatabase != null || isFileLoaded)
            {
                throw new SystemException("Load failed. File already loaded.");
            }
            else if (!File.Exists(filename))
            {
                throw new SystemException("Load failed. Filename doesn't exist.");
            }
            else if (File.Exists(filename))
            {
                xmlDatabase = new XmlDocument();
                xmlDatabase.Load(filename);
            }

            isFileLoaded = true;
            xmlDatabaseFilename = filename;

            if (!validate())
            {
                Console.WriteLine("The file \"" + filename + "\" is invalid because it is not formatted correctly.");
                unload();
            }
        }

        /// <summary>
        /// Unloads any loaded .xml file. 
        /// </summary>
        public void unload()
        {
            xmlDatabase = null;
            xmlDatabaseFilename = "";
            isFileLoaded = false;
            isValidated = false;
        }

        /// <summary>
        /// Makes sure that the loaded file contains the required xml tags.
        /// 
        /// Returns true if the loaded file is valid, false otherwise.
        /// </summary>
        /// <returns>True if the loaded file is valid, false otherwise.</returns>
        public bool validate()
        {
            if (isFileLoaded)
            {
                TagCheck flags = new TagCheck();

                flags = flags | (xmlDatabase.SelectSingleNode("/root") == null ? 0 : TagCheck.hasRootTag);
                flags = flags | (xmlDatabase.SelectSingleNode("/root/users") == null ? 0 : TagCheck.hasUsersTag);
                flags = flags | (xmlDatabase.SelectSingleNode("/root/usergroups") == null ? 0 : TagCheck.hasUsergroupsTag);
                flags = flags | (xmlDatabase.SelectSingleNode("/root/devices") == null ? 0 : TagCheck.hasDevicesTag);
                flags = flags | (xmlDatabase.SelectSingleNode("/root/compartments") == null ? 0 : TagCheck.hasCompartmentsTag);
                flags = flags | (xmlDatabase.SelectSingleNode("/root/assets") == null ? 0 : TagCheck.hasAssetsTag);
                flags = flags | (xmlDatabase.SelectSingleNode("/root/logs") == null ? 0 : TagCheck.hasLogsTag);
                flags = flags | (xmlDatabase.SelectSingleNode("/root/ugToComp") == null ? 0 : TagCheck.hasUgToCompTag);
                flags = flags | (xmlDatabase.SelectSingleNode("/root/settings") == null ? 0 : TagCheck.hasSettingsTag);

                return isValidated = (flags == TagCheck.hasAllTags ? true : false);
            }

            return false;
        }

        /// <summary>
        /// Saves the current changes to the .xml file.
        /// </summary>
        public void save()
        {
            if (isFileLoaded)
            {
                xmlDatabase.Save(xmlDatabaseFilename);
            }
        }

        /// <summary>
        /// Saves the current changes to a file at the passed in filename. If the file exists, IT WILL BE OVERWRITTEN.
        /// </summary>
        /// <param name="filename"></param>
        public void saveAs(String filename)
        {
            if (isFileLoaded)
            {
                xmlDatabase.Save(filename);
            }
        }

        /// <summary>
        /// Prints the entire current .xml structure to the Console. 
        /// </summary>
        public void printXml()
        {
            if (isFileLoaded)
            {
                Console.WriteLine(xmlDatabaseFilename);
                xmlDatabase.Save(Console.Out);
                Console.WriteLine();

                return;
            }

            Console.WriteLine("No file loaded.");
        }

        /// <summary>
        /// Prints the entire xml structure to the Console starting from the parent element. 
        /// </summary>
        /// <param name="parent">The parent object to print.</param>
        public void printXml(XmlElement parent)
        {
            if (!isFileLoaded)
            {
                Console.WriteLine("No file loaded.");
                return;
            }
            if (parent.NodeType == XmlNodeType.Element)
            {
                XmlTextWriter consoleWriter = new XmlTextWriter(Console.Out);
                consoleWriter.Formatting = Formatting.Indented;
                parent.WriteTo(consoleWriter);
                Console.WriteLine();
            }
            else if (parent.NodeType == XmlNodeType.Text)
            {
                Console.WriteLine(parent.Value);
            }
        }

        /// <summary>
        /// Prints the XmlNodes in the passed in XmlNodeList. 
        /// </summary>
        /// <param name="list">The collection of XmlNodes to print.</param>
        public void printXml(XmlNodeList list)
        {
            if (!isFileLoaded)
            {
                return;
            }

            XmlTextWriter consoleWriter = new XmlTextWriter(Console.Out);
            consoleWriter.Formatting = Formatting.Indented;
            foreach (XmlNode node in list)
            {
                node.WriteTo(consoleWriter);
            }

            Console.WriteLine();
        }

        /// <summary>
        /// Returns the filename of the currently loaded file. 
        /// </summary>
        /// <returns>The filename of the currently loaded file.</returns>
        public override String ToString()
        {
            if (isFileLoaded)
            {
                return ("XmlDatabase using file: " + xmlDatabaseFilename);
            }
            return ("No .xml file loaded.");
        }
    #endregion

        #region Parsing Functions

        private XmlElement getChildByAttribute(XmlElement parent, String attribute, String value)
    {
        foreach (XmlElement child in parent.ChildNodes)
        {
            if (child.GetAttribute(attribute).Equals(value))
            {
                return child;
            }
        }
        return null;
    }

        /// <summary>
    /// Checks the child elements of the parent for the specified ID attribute.
    /// 
    /// Returns true if a child element with that ID exists. 
    /// </summary>
    /// <param name="parent">The parent whose child elements to check.</param>
    /// <param name="id">The ID to check for.</param>
    /// <returns>True if a child element with that ID exists.</returns>
        public bool doesChildExistWithId(int id)
        {
            if (isFileLoaded)
            {
                foreach (XmlElement user in xmlDatabase.SelectNodes("users"))
                {
                    if (user.GetAttribute("id").Equals(id.ToString()))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        /// <summary>
    /// Gets the current primary key value for the group. 
    /// 
    /// 'group' should either be users, devices, or logs.
    /// 
    /// Returns the primary key to be assigned next, if the group is valid.
    /// </summary>
    /// <param name="group">Should either be users, devices, or logs. </param>
    /// <returns>The primary key to be assigned next, if the group is valid.</returns>
        private String getPrimaryKey(XmlElement group)
        {
            return group["pKey"] == null ? "" : group["pKey"].InnerText;
        }

        /// <summary>
        /// Gets the current primary key value for the group, then increments it. 
        /// 
        /// 'group' should either be a second level element such as users or devices.
        /// 
        /// Returns the primary key to be assigned next, if the group is valid.
        /// </summary>
        /// <param name="group">The group to get the primary key from.</param>
        /// <returns>The primary key to be assigned next, if the group is valid.</returns>
        private String getPrimaryKeyAndIncrement(XmlElement group)
        {
            String pKey = group["pKey"].InnerText;
            group["pKey"].InnerText = (Convert.ToInt32(group["pKey"].InnerText) + 1).ToString();
            return pKey;
        }

        private bool isAProperty(String property)
        {
            if (!property.Equals(COMPID) ||
                !property.Equals(DEVICEID) ||
                !property.Equals(ID) ||
                !property.Equals(USERID) ||
                !property.Equals(USERGROUPID) ||
                !property.Equals(ACTIVE) ||
                !property.Equals(ASSETID) ||
                !property.Equals(CONTENT) ||
                !property.Equals(FNAME) ||
                !property.Equals(LNAME) ||
                !property.Equals(NAME) ||
                !property.Equals(PASSWORD) ||
                !property.Equals(RFID) ||
                !property.Equals(TIME) ||
                !property.Equals(TYPE) ||
                !property.Equals(USERNAME))
            {
                return false;
            }
            return true;
        }

        private ArrayList getChildrenWhere(String group, String property, String value)
        {
            int valueInt = Convert.ToInt32(value);

            if (isFileLoaded && isAProperty(property))
            {
                ArrayList targetList = new ArrayList();
                if (group.Equals("users"))
                {
                    switch (property)
                    {
                        case USERNAME: targetList.Add(getUserByUsername(value));
                            break;
                        case USERGROUPID: targetList.AddRange(getUsersByUsergroup(valueInt));
                            break;
                        default: break;
                    }
                }
                else if (group.Equals("usergroups"))
                {
                    switch (property)
                    {
                        case ID: targetList.Add(getUsergroupById(valueInt));
                            break;
                        default: break;
                    }
                }
                else if (group.Equals("devices"))
                {
                    switch (property)
                    {
                        case ID: targetList.Add(getDeviceById(valueInt));
                            break;
                        default: break;
                    }
                }
                else if (group.Equals("compartments"))
                {
                    switch (property)
                    {
                        case ID: targetList.Add(getCompartmentById(valueInt));
                            break;
                        default: break;
                    }
                }
                else if (group.Equals("assets"))
                {
                    switch (property)
                    {
                        case ID: targetList.Add(getAssetById(valueInt));
                            break;
                        case USERID: targetList.AddRange(getAssetsByUser(valueInt));
                            break;
                        case COMPID: targetList.AddRange(getAssetsByCompartment(valueInt));
                            break;
                        default: break;
                    }
                }
                else if (group.Equals("logs"))
                {
                    switch (property)
                    {
                        case ID: targetList.Add(getEventById(valueInt));
                            break;
                        case COMPID: targetList.AddRange(getEventsByCompartment(valueInt));
                            break;
                        case USERID: targetList.AddRange(getEventsByUser(valueInt));
                            break;
                        default: break;
                    }
                }
                return targetList;
            }
            return null;
        }
    #endregion

        #region User Functions

        /// <summary>
        /// Adds a new user. 
        /// 
        /// Returns the user's id if adding succeeded, 0 otherwise.
        /// </summary>
        /// <param name="username">The username of the user.</param>
        /// <param name="password">The password of the user.</param>
        /// <param name="firstName">The first name of the user.</param>
        /// <param name="lastName">The last name of the user.</param>
        /// <param name="usergroupId">The ID of the usergroup the user belongs so.</param>
        /// <returns>True if adding succeeded, false otherwise. </returns>
        public int addUser(String username, String password, String rfid, String firstName, String lastName, int usergroupId, int active)
        {
            if (isFileLoaded)
            {
                XmlElement newUser = xmlDatabase.CreateElement(USER);

                newUser.SetAttribute(ID, getPrimaryKeyAndIncrement(users));
                newUser.InnerXml = "<" + USERNAME + ">" + username + "</" + USERNAME + ">";
                newUser.InnerXml += "<" + PASSWORD + ">" + password + "</" + PASSWORD + ">";
                newUser.InnerXml += "<" + RFID + ">" + rfid + "</" + RFID + ">";
                newUser.InnerXml += "<" + FNAME + ">" + firstName + "</" + FNAME + ">";
                newUser.InnerXml += "<" + LNAME + ">" + lastName + "</" + LNAME + ">";
                newUser.InnerXml += "<" + USERGROUPID + ">" + usergroupId + "</" + USERGROUPID + ">";
                newUser.InnerXml += "<" + ACTIVE + ">" + active + "</" + ACTIVE + ">";
                users.AppendChild(newUser);

                Console.WriteLine("Added new user \"" + username + "\"/\"" + firstName + " " + lastName + "\" (id = " + newUser.GetAttribute(ID) + ")");
                return Convert.ToInt32(newUser.GetAttribute(ID));
            }
            return 0;
        }

        public bool addUser(User newUser)
        {
            if (isFileLoaded)
            {
                XmlElement addUser = xmlDatabase.CreateElement(USER);
                addUser.SetAttribute(ID, getPrimaryKeyAndIncrement(users));

                addUser.InnerXml = "<" + USERNAME + ">" + newUser.Username + "</" + USERNAME + ">";
                addUser.InnerXml += "<" + PASSWORD + ">" + newUser.Password + "</" + PASSWORD + ">";
                addUser.InnerXml += "<" + RFID + ">" + newUser.RFID + "</" + RFID + ">";
                addUser.InnerXml += "<" + FNAME + ">" + newUser.FirstName + "</" + FNAME + ">";
                addUser.InnerXml += "<" + LNAME + ">" + newUser.LastName + "</" + LNAME + ">";
                addUser.InnerXml += "<" + USERGROUPID + ">" + newUser.UserGroup + "</" + USERGROUPID + ">";
                addUser.InnerXml += "<" + ACTIVE + ">" + newUser.IsActive + "</" + RFID + ">";

                users.AppendChild(addUser);

                Console.WriteLine("Added new user \"" + newUser.Username + "\"/\"" + newUser.FirstName + " " +
                    newUser.LastName + "\" (id = " + addUser.GetAttribute(ID) + ")");
                return true;
            }

            return false;
        }

        /// <summary>
        /// Removes a user by ID.
        /// 
        /// Returns true if removal succeeded, false otherwise.
        /// </summary>
        /// <param name="userId">The ID of the user to remove.</param>
        /// <returns>True if removal succeeded, false otherwise. </returns>
        public User removeUser(int userId)
        {
            if (isFileLoaded)
            {
                XmlElement userToRemove = getChildByAttribute(users, ID, userId.ToString());

                if (userToRemove != null)
                {
                    User removedUser = new User(userToRemove);

                    users.RemoveChild(userToRemove);
                    Console.WriteLine("Removed user \"" + userToRemove.SelectSingleNode(USERNAME).InnerText +
                        "\"/\"" + userToRemove[FNAME].InnerText + " " + userToRemove[LNAME].InnerText +
                        "\" (id = " + userId + ")");

                    return removedUser;
                }
            }
            return null;
        }

        /// <summary>
        /// Removes a user by username. Returns true is removal succeeded, false otherwise.
        /// </summary>
        /// <param name="username">The username of the user to remove.</param>
        /// <returns>True is removal succeeded, false otherwise.</returns>
        public User removeUser(String username)
        {
            if (isFileLoaded)
            {
                XmlElement userToRemove = (XmlElement)users.SelectSingleNode(USER + "[" + USERNAME + "=" + username + "]");

                if (userToRemove != null)
                {
                    User removedUser = new User(userToRemove);
                    users.RemoveChild(userToRemove);
                    Console.WriteLine("Removed user \"" + username + "\"/\"" + userToRemove[FNAME].InnerText + " " +
                        userToRemove[LNAME].InnerText + "\" (id = " + userToRemove.GetAttribute(ID) + ")");
                    return removedUser;
                }
            }
            return null;
        }

        public User removeUser(User removeUser)
        {
            if (isFileLoaded)
            {
                XmlElement userToRemove = getChildByAttribute(users, ID, removeUser.ID.ToString());

                if (userToRemove != null)
                {
                    users.RemoveChild(userToRemove);
                    Console.WriteLine("Removed user \"" + userToRemove.SelectSingleNode(USERNAME).InnerText +
                        "\"/\"" + userToRemove[FNAME].InnerText + " " + userToRemove[LNAME].InnerText +
                        "\" (id = " + userToRemove.GetAttribute(ID) + ")");

                    return removeUser;
                }
            }
            return null;
        }

        /// <summary>
        /// Renames the user with the passed in ID. 
        /// </summary>
        /// <param name="userId">The ID of the user to edit.</param>
        /// <param name="newFirstName">The new firstName to give the user.</param>
        /// <param name="newLastName">The new firstName to give the user.</param>
        public void updateUserName(int userId, String newFirstName, String newLastName)
        {
            if (isFileLoaded)
            {
                if (doesUserIdExist(userId))
                {
                    XmlElement editUser = getChildByAttribute(users, ID, userId.ToString());
                    String oldFirstName = editUser[FNAME].InnerText;
                    String oldLastName = editUser[LNAME].InnerText;

                    editUser[FNAME].InnerText = newFirstName;
                    editUser[LNAME].InnerText = newLastName;

                    Console.WriteLine("User \"" + oldFirstName + " " + oldLastName + "\" (id = " + userId +
                        ") renamed to \"" + newFirstName + " " + newLastName + "\"");
                }
            }
        }

        /// <summary>
        /// Renames the user with the specified username. 
        /// </summary>
        /// <param name="username">The username of the user to edit.</param>
        /// <param name="newFirstName">The new firstName to give the user.</param>
        /// <param name="newLastName">The new firstName to give the user.</param>
        public void updateUserName(String username, String newFirstName, String newLastName)
        {
            if (isFileLoaded)
            {
                if (doesUserUsernameExist(username))
                {
                    XmlElement editUser = (XmlElement)users.SelectSingleNode(USER + "[" + USERNAME + "=" + username + "]");
                    String oldFirstName = editUser[FNAME].InnerText;
                    String oldLastName = editUser[LNAME].InnerText;
                    editUser[FNAME].InnerText = newFirstName;
                    editUser[LNAME].InnerText = newLastName;

                    Console.WriteLine("User \"" + username + "\"/\"" + oldFirstName + " " + oldLastName +
                        "\" (id = " + editUser.GetAttribute(ID) + ") renamed to \"" + newFirstName + " " + newLastName + "\"");
                }
            }
        }

        /// <summary>
        /// Change a user's username. 
        /// </summary>
        /// <param name="id">The ID of the user to edit.</param>
        /// <param name="newUsername">The username to change to.</param>
        public void updateUserUsername(int id, String newUsername)
        {
            if (isFileLoaded)
            {
                XmlElement editUser = getChildByAttribute(users, ID, id.ToString());
                String oldUsername = editUser[USERNAME].InnerText;
                editUser[USERNAME].InnerText = newUsername;

                Console.WriteLine("Username \"" + oldUsername + "\" (id = " + editUser.GetAttribute(ID) +
                    ") changed to \"" + newUsername + "\"");
            }
        }

        /// <summary>
        /// Change a user's username. 
        /// </summary>
        /// <param name="username">The username of the user to edit.</param>
        /// <param name="newUsername">The username to change to.</param>
        public void updateUserUsername(String username, String newUsername)
        {
            if (isFileLoaded)
            {
                XmlElement editUser = (XmlElement)users.SelectSingleNode(USER + "[" + USERNAME + "=" + username + "]");
                editUser[USERNAME].InnerText = newUsername;

                Console.WriteLine("Username \"" + username + "\" (id = " + editUser.GetAttribute(ID) +
                    ") changed to \"" + newUsername + "\"");
            }
        }

        /// <summary>
        /// Changes a user's password.
        /// </summary>
        /// <param name="id">The user's ID. </param>
        /// <param name="newPassword">The new password.</param>
        public void updateUserPassword(int id, String newPassword)
        {
            if (isFileLoaded)
            {
                XmlElement editUser = getChildByAttribute(users, ID, id.ToString());
                editUser[PASSWORD].InnerText = newPassword;

                Console.WriteLine("Password changed for user \"" + editUser[USERNAME] + "\"/\"" +
                    editUser[FNAME] + " " + editUser[LNAME] + "\" (id = " + editUser.GetAttribute(ID) + ")");
            }
        }

        /// <summary>
        /// Changes a user's password.
        /// </summary>
        /// <param name="username">The user's username. </param>
        /// <param name="newPassword">The new password.</param>
        public void updateUserPassword(String username, String newPassword)
        {
            if (isFileLoaded)
            {
                XmlElement editUser = (XmlElement)users.SelectSingleNode(USER + "[" + USERNAME + "=" + username + "]");
                editUser[PASSWORD].InnerText = newPassword;

                Console.WriteLine("Password changed for user \"" + editUser[USERNAME] + "\"/\"" +
                    editUser[FNAME] + " " + editUser[LNAME] + "\" (id = " + editUser.GetAttribute(ID) + ")");
            }
        }

        /// <summary>
        /// Changes the passed in user's to the passed in usergroup. 
        /// </summary>
        /// <param name="userId">The ID of the user to edit.</param>
        /// <param name="newUsergroupId">The ID of the usergroup to change to.</param>
        public void updateUserUsergroup(int userId, int newUsergroupId)
        {
            if (isFileLoaded)
            {
                if (doesUserIdExist(userId) && doesUsergroupIdExist(newUsergroupId))
                {
                    XmlElement editUser = getUserXmlById(userId);
                    int oldUsergroupId = Convert.ToInt32(editUser[USERGROUPID].InnerText);
                    editUser[USERGROUPID].InnerText = newUsergroupId.ToString();

                    Console.WriteLine("User \"" + editUser[FNAME].InnerText + " " +
                        editUser[LNAME].InnerText + "\" (id = " + userId +
                        ") usergroup changed from \"" + getUsergroupXmlById(oldUsergroupId).Name +
                        " (id = " + oldUsergroupId + ") to " + getUsergroupXmlById(newUsergroupId).Name +
                        "\" (id = " + newUsergroupId + ")");
                }
            }

        }

        /// <summary>
        /// Checks all users for the specified ID.
        /// 
        /// Returns true if a user with the specified ID exists. 
        /// </summary>
        /// <param name="id">The ID to check for.</param>
        /// <returns>True if a user with the specified ID exists. </returns>
        public bool doesUserIdExist(int id)
        {
            if (isFileLoaded)
            {
                // note: can use SelectSingleNode() because IDs are assumed to be unique
                XmlNode result = users.SelectSingleNode("*[@" + ID + "=" + id + "]");
                return result == null ? false : true;
            }
            return false;
        }

        /// <summary>
        /// Returns true if a user with the specified username exists. 
        /// </summary>
        /// <param name="username">The username to search for.</param>
        /// <returns>True if a user with the specified username exists.</returns>
        public bool doesUserUsernameExist(String username)
        {
            if (isFileLoaded)
            {
                // note: can use SelectSingleNode() because usernames are assumed to be unique
                XmlNode result = users.SelectSingleNode(USER + "[" + USERNAME + "=" + username + "]");
                return result == null ? false : true;
            }

            return false;
        }

        public XmlElement getUserXmlById(int id)
        {
            return isFileLoaded ? getChildByAttribute(users, ID, id.ToString()) : null;
        }

        public XmlElement getUserXmlByUsername(String username)
        {
            if (isFileLoaded)
            {
                XmlNode target = users.SelectSingleNode(USER + "[" + USERNAME + "=" + username + "]");
                return (XmlElement)target ?? null;
            }

            return null;
        }

        public XmlElement getUserXmlByRfid(string rfid)
        {
            if (isFileLoaded)
            {
                XmlNode target = users.SelectSingleNode(USER + "[" + RFID + "=" + rfid + "]");
                return (XmlElement)target ?? null;
            }
            return null;
        }

        /// <summary>
        /// Gets an XmlNodeList of users with the specified usergroup. Returns null if users don't exist.
        /// </summary>
        /// <param name="usergroupId">The ID to search for.</param>
        /// <returns>The XmlNodeList of matching users or null if users don't exist.</returns>
        public XmlNodeList getUserXmlByUsergroupId(int usergroupId)
        {
            if (isFileLoaded)
            {
                XmlNodeList li = users.SelectNodes(USER + "[" + USERGROUPID + "=" + usergroupId + "]");
                return li.Count > 0 ? li : null;
            }

            return null;
        }

        /// <summary>
        /// Gets the user with the specified ID. Returns null if user doesn't exist.
        /// </summary>
        /// <param name="id">The ID to search for.</param>
        /// <returns>The XmlElement for that user or null if user doesn't exist.</returns>
        public User getUserById(int id)
        {
            return isFileLoaded ? new User(getChildByAttribute(users, ID, id.ToString())) : null;
        }

        public User getUserByUsername(String username)
        {
            if (isFileLoaded)
            {
                XmlNode target = users.SelectSingleNode(USER + "[" + USERNAME + "=" + username + "]");
                if (target != null)
                {
                    return new User(target);
                }
            }

            return null;
        }


        public User getUserByRfid(string rfid)
        {
            if (isFileLoaded)
            {
                XmlNode target = users.SelectSingleNode(USER + "[" + RFID + "=" + rfid + "]");
                if (target != null)
                {
                    return new User(target);
                }
            }
            return null;
        }

        public List<User> getUsersByUsergroup(int usergroupId)
        {
            if (isFileLoaded)
            {
                XmlNodeList li = users.SelectNodes(USER + "[" + USERGROUPID + "=" + usergroupId + "]");           
                if (li.Count > 0)
                {
                    List<User> userList = new List<User>(li.Count);
                    foreach (XmlNode xn in li)
                    {
                        userList.Add(xn);
                    }
                    return userList;
                }
            }

            return null;
        }

        public ArrayList getUsersWhere(String property, String value)
        {
            return this.getChildrenWhere("users", property, value);
        }

    #endregion

        #region Usergroup Functions
        /// <summary>
        /// Adds a new usergroup by name.
        /// 
        /// Returns the usergroup's id if adding succeeded, 0 otherwise.
        /// </summary>
        /// <param name="name">The name of the usergroup to add.</param>
        /// <returns>True if adding succeeded, false otherwise. </returns>
        public int addUsergroup(String name)
        {
            if (isFileLoaded)
            {
                XmlElement newUsergroup = xmlDatabase.CreateElement(USERGROUP);
                newUsergroup.SetAttribute(ID, getPrimaryKeyAndIncrement(usergroups));
                newUsergroup.InnerXml = "<" + NAME + ">" + name + "</" + NAME + ">";
                usergroups.AppendChild(newUsergroup);

                Console.WriteLine("Added new usergroup \"" + name + "\" (id = " + newUsergroup.GetAttribute(ID) + ")");
                return Convert.ToInt32(newUsergroup.GetAttribute(ID));
            }

            return 0;
        }

        /// <summary>
        /// Removes a usergroup by ID.
        /// 
        /// Returns true if removal succeeded, false otherwise.
        /// </summary>
        /// <param name="usergroupId">The ID of the usergroup to remove.</param>
        /// <returns>True if removal succeeded, false otherwise. </returns>
        public Usergroup removeUsergroup(int usergroupId)
        {
            if (isFileLoaded)
            {
                XmlElement usergroupToRemove = getChildByAttribute(usergroups, ID, usergroupId.ToString());

                if (usergroupToRemove != null)
                {
                    Usergroup removedUG = new Usergroup(usergroupToRemove);
                    usergroups.RemoveChild(usergroupToRemove);
                    Console.WriteLine("Removed usergroup \"" + usergroupToRemove[NAME].InnerText +
                        "\" (id = " + usergroupId + ")");
                    return removedUG;
                }
            }
            return null;
        }

        /// <summary>
        /// Renames the usergroup with the passed in ID. 
        /// </summary>
        /// <param name="usergroupId">The ID of the usergroup to edit.</param>
        /// <param name="newName">The new name to give the usergroup.</param>
        public void updateUsergroupName(int usergroupId, String newName)
        {
            if (isFileLoaded)
            {
                if (doesUsergroupIdExist(usergroupId))
                {
                    XmlElement editUsergroup = getChildByAttribute(usergroups, ID, usergroupId.ToString());
                    String oldName = editUsergroup[NAME].InnerText;

                    editUsergroup[NAME].InnerText = newName;

                    Console.WriteLine("Usergroup \"" + oldName + "\" (id = " + usergroupId +
                        ") renamed to \"" + newName + "\"");
                }
            }
        }

        /// <summary>
        /// Checks all usergroups for the specified ID.
        /// 
        /// Returns true if a usergroup with the specified ID exists. 
        /// </summary>
        /// <param name="id">The ID to check for.</param>
        /// <returns>True if a usergroup with the specified ID exists. </returns>
        public bool doesUsergroupIdExist(int id)
        {
            if (isFileLoaded)
            {
                // note: can use SelectSingleNode() because IDs are assumed to be unique
                XmlNode result = usergroups.SelectSingleNode("*[@" + ID + "=" + id + "]");
                return result == null ? false : true;
            }
            return false;
        }

        /// <summary>
        /// Gets the usergroup with the specified ID. Returns null if usergroup doesn't exist.
        /// </summary>
        /// <param name="id">The ID to search for.</param>
        /// <returns>The XmlElement for that usergroup or null if user doesn't exist.</returns>
        public XmlElement getUsergroupXmlById(int id)
        {
            return isFileLoaded ? getChildByAttribute(usergroups, ID, id.ToString()) : null;
        }

        public Usergroup getUsergroupById(int id)
        {
            return isFileLoaded ? new Usergroup(getChildByAttribute(usergroups, ID, id.ToString())) : null;
        }

        public ArrayList getUsergroupsWhere(String property, String value)
        {
            return this.getChildrenWhere("usergroups", property, value);
        }
    #endregion

        #region Usergroup to Compartment functions
        public int addUsergroupToCompartmentRelation(int usergroupId, int compartmentId)
        {
            if (isFileLoaded && !doesUsergroupToCompartmentRelationExist(usergroupId, compartmentId))
            {
                XmlElement newRelation = xmlDatabase.CreateElement("relation");
                newRelation.SetAttribute(ID, this.getPrimaryKeyAndIncrement(ugToComp));
                newRelation.InnerXml += "<" + USERGROUPID + ">" + usergroupId + "</" + USERGROUPID + ">";
                newRelation.InnerXml += "<" + COMPID + ">" + compartmentId + "</" + COMPID + ">";

                ugToComp.AppendChild(newRelation);
                return Convert.ToInt32(newRelation.GetAttribute(ID));
            }
            return 0;
        }

        public bool removeUsegroupToCompartmentRelation(int usergroupId, int compartmentId)
        {
            if (isFileLoaded && doesUsergroupToCompartmentRelationExist(usergroupId, compartmentId))
            {
                XmlNode relationToRemove = ugToComp.SelectSingleNode("relation[" + USERGROUPID + "=" + usergroupId + " and " + COMPID + "=" + compartmentId + "]");
                ugToComp.RemoveChild(relationToRemove);
                return true;
            }
            return false;
        }

        public bool doesUsergroupToCompartmentRelationExist(int usergroupId, int compartmentId)
        {
            if (isFileLoaded)
            {
                XmlNodeList li = ugToComp.SelectNodes("relation[" + USERGROUPID + "=" + usergroupId + " and " + COMPID + "=" + compartmentId + "]");

                return (li.Count > 0) ? true : false;
            }
            return false;
        }

        public bool doesRelationExistWithUsergroupId(int usergroupId)
        {
            if (isFileLoaded)
            {
                XmlNodeList li = ugToComp.SelectNodes("relation[" + USERGROUPID + "=" + usergroupId + "]");
                return (li.Count > 0) ? true : false;
            }
            return false;
        }

        public bool doesRelationExistWithCompartmentId(int compartmentId)
        {
            if (isFileLoaded)
            {
                XmlNodeList li = ugToComp.SelectNodes("relation[" + COMPID + "=" + compartmentId + "]");
                return (li.Count > 0) ? true : false;
            }
            return false;
        }

        public List<Compartment> getCompartmentsForUsergroup(int usergroupId)
        {
            if (isFileLoaded && doesRelationExistWithUsergroupId(usergroupId))
            {
                XmlNodeList matches = ugToComp.SelectNodes("relation[" + USERGROUPID + "=" + usergroupId + "]");
                List<Compartment> li = new List<Compartment>();
                foreach(XmlNode xn in matches)
                {
                    li.Add(getCompartmentById(Convert.ToInt32(xn.SelectSingleNode(COMPID).InnerText)));
                }
                return li;
            }

            return null;
        }

        public List<Usergroup> getUsergroupsForCompartment(int compartmentId)
        {
            if (isFileLoaded && doesRelationExistWithCompartmentId(compartmentId))
            {
                XmlNodeList matches = ugToComp.SelectNodes("relation[" + COMPID + "=" + compartmentId + "]");
                List<Usergroup> li = new List<Usergroup>();
                foreach (XmlNode xn in matches)
                {
                    li.Add(getUsergroupById(Convert.ToInt32(xn.SelectSingleNode(USERGROUPID).InnerText)));
                }
                return li;
            }

            return null;
        }

        public void removeUgToCompByUsergroupId(int usergroupId)
        {
            String ug = usergroupId.ToString();
            XmlNodeList li = ugToComp.SelectNodes("relation[" + USERGROUPID + "=" + ug + "]");
            foreach (XmlNode xn in li)
            {
                ugToComp.RemoveChild(xn);
            }
        }

        public void removeUgToCompByCompartmentId(int compartmentId)
        {
            String cp = compartmentId.ToString();
            XmlNodeList li = ugToComp.SelectNodes("relation[" + COMPID + "=" + cp + "]");
            foreach (XmlNode xn in li)
            {
                ugToComp.RemoveChild(xn);
            }
        }

    #endregion

        #region Device Functions
        /// <summary>
        /// Adds a new device by deviceType and ID.
        /// 
        /// Returns the device's id if adding succeeded, 0 otherwise.
        /// </summary>
        /// <param name="deviceName">The name of the device.</param>
        /// <param name="deviceType">The deviceType of the device to add.</param>
        /// <param name="deviceId">The ID of the device to add.</param>
        /// <returns>True if adding succeeded, false otherwise. </returns>
        public int addDevice(String deviceName, String deviceType, int compartmentId, String deviceSerial, int deviceLibId)
        {
            if (isFileLoaded)
            {
                // check if id already in use
                if (doesDeviceSerialExist(deviceSerial))
                {
                    Console.WriteLine("Could not add new device \"" + deviceType + "\". A device with Serial = " + deviceSerial + " already exists.");
                    return 0;
                }

                XmlElement newDevice = xmlDatabase.CreateElement(DEVICE);
                newDevice.SetAttribute(ID, this.getPrimaryKeyAndIncrement(devices));
                newDevice.InnerXml += "<" + NAME + ">" + deviceName + "</" + NAME + ">";
                newDevice.InnerXml += "<" + TYPE + ">" + deviceType + "</" + TYPE + ">";
                newDevice.InnerXml += "<" + COMPID + ">" + compartmentId + "</" + COMPID + ">";
                newDevice.InnerXml += "<" + SERIAL + ">" + deviceSerial + "</" + SERIAL + ">";
                newDevice.InnerXml += "<" + LIBID + ">" + deviceLibId + "</" + LIBID + ">";
                devices.AppendChild(newDevice);
                Console.WriteLine("Added new device \"" + deviceType + "\" (id = " + newDevice.GetAttribute(ID) + ")");

                return Convert.ToInt32(newDevice.GetAttribute(ID));
            }

            return 0;
        }

        /// <summary>
        /// Removes a device by ID.
        /// 
        /// Returns true if removal succeeded, false otherwise.
        /// </summary>
        /// <param name="deviceId">The ID of the device to remove.</param>
        /// <returns>True if removal succeeded, false otherwise. </returns>
        public Device removeDevice(int deviceId)
        {
            if (isFileLoaded)
            {
                XmlNode deviceToRemove = getChildByAttribute(devices, ID, deviceId.ToString());

                if (deviceToRemove != null)
                {
                    Device removedDevice = new Device(deviceToRemove);
                    devices.RemoveChild(deviceToRemove);
                    Console.WriteLine("Removed device \"" + deviceToRemove[TYPE].InnerText + "\" (id = " + deviceId + ")");
                    return removedDevice;
                }
            }
            return null;
        }

        /// <summary>
        /// Checks all devices for the specified ID.
        /// 
        /// Returns true if a device with the specified ID exists. 
        /// </summary>
        /// <param name="id">The ID to check for.</param>
        /// <returns>True if a device with the specified ID exists. </returns>
        public bool doesDeviceIdExist(int id)
        {
            if (isFileLoaded)
            {
                // note: can use SelectSingleNode() because IDs are assumed to be unique
                XmlNode match = devices.SelectSingleNode("*[@" + ID + "=" + id + "]");
                return match == null ? false : true;
            }
            return false;
        }

        public bool doesDeviceSerialExist(String serial)
        {
            if (isFileLoaded)
            {
                XmlNode match = devices.SelectSingleNode(DEVICE + "[" + SERIAL + "=" + serial + "]");
                return match == null ? false : true;
            }
            return false;
        }

        /// <summary>
        /// Gets the device with the specified ID. Returns null if device doesn't exist.
        /// </summary>
        /// <param name="id">The ID to search for.</param>
        /// <returns>The XmlElement for that user or null if device doesn't exist.</returns>
        public XmlElement getDeviceXmlById(int id)
        {
            return isFileLoaded ? getChildByAttribute(devices, ID, id.ToString()) : null;
        }

        public Device getDeviceById(int id)
        {
            return isFileLoaded ? new Device(getChildByAttribute(devices, ID, id.ToString())) : null;
        }

        public List<Device> getDeviceByCompartmentId(int compartmentId)
        {
            if (isFileLoaded)
            {
                XmlNodeList matches = devices.SelectNodes(DEVICE + "[" + COMPID + "=" + compartmentId + "]");
                List<Device> li = new List<Device>();
                foreach (XmlNode xn in matches)
                {
                    li.Add(new Device(xn));
                }
                return li;
            }
            return null;
        }

        public ArrayList getDevicesWhere(String property, String value)
        {
            return this.getChildrenWhere("devices", property, value);
        }

    #endregion

        #region Compartment Functions
        /// <summary>
        /// Adds a new compartment by name.
        /// 
        /// Returns the compartment's id if adding succeeded, 0 otherwise.
        /// </summary>
        /// <param name="name">The name of the compartment to add.</param>
        /// <returns>True if adding succeeded, false otherwise. </returns>
        public int addCompartment(String name, String passcode)
        {
            if (isFileLoaded)
            {
                XmlElement newCompartment = xmlDatabase.CreateElement(COMPARTMENT);

                newCompartment.SetAttribute(ID, getPrimaryKeyAndIncrement(compartments));
                newCompartment.InnerXml += "<" + NAME + ">" + name + "</" + NAME + ">";
                newCompartment.InnerXml += "<" + PASSCODE + ">" + passcode + "</" + PASSCODE + ">";
                compartments.AppendChild(newCompartment);
                Console.WriteLine("Added new compartment \"" + name + "\" (id = " + newCompartment.GetAttribute(ID) + ")");

                return Convert.ToInt32(newCompartment.GetAttribute(ID));
            }
            return 0;
        }

        /// <summary>
        /// Removes a compartment by ID.
        /// 
        /// Returns true if removal succeeded, false otherwise.
        /// </summary>
        /// <param name="compartmentId">The ID of the compartment to remove.</param>
        /// <returns>True if removal succeeded, false otherwise. </returns>
        public Compartment removeCompartment(int compartmentId)
        {
            if (isFileLoaded)
            {
                XmlElement compartmentToRemove = getChildByAttribute(compartments, ID, compartmentId.ToString());

                if (compartmentToRemove != null)
                {
                    Compartment removedCompartment = new Compartment(compartmentToRemove);
                    compartments.RemoveChild(compartmentToRemove);
                    Console.WriteLine("Removed compartment \"" + compartmentToRemove[NAME].InnerText + " "
                        + "\" (id = " + compartmentId + ")");
                    return removedCompartment;
                }
            }
            return null;
        }

        /// <summary>
        /// Renames the compartment with the passed in ID. 
        /// </summary>
        /// <param name="compartmentId">The ID of the compartment to edit.</param>
        /// <param name="newName">The new name to give the compartment.</param>
        public void updateCompartmentName(int compartmentId, String newName)
        {
            if (isFileLoaded)
            {
                if (doesCompartmentIdExist(compartmentId))
                {
                    XmlElement editLocation = getChildByAttribute(compartments, ID, compartmentId.ToString());
                    String oldName = editLocation[NAME].InnerText;
                    editLocation[NAME].InnerText = newName;
                    Console.WriteLine("Location \"" + oldName + "\" (id = " + compartmentId +
                        ") renamed to \"" + newName + "\"");
                }
            }
        }

        /// <summary>
        /// Checks all compartments for the specified ID.
        /// 
        /// Returns true if a compartment with the specified ID exists. 
        /// </summary>
        /// <param name="id">The ID to check for.</param>
        /// <returns>True if a compartment with the specified ID exists. </returns>
        public bool doesCompartmentIdExist(int id)
        {
            if (isFileLoaded)
            {
                // note: can use SelectSingleNode() because IDs are assumed to be unique
                XmlNode result = compartments.SelectSingleNode("*[@" + ID + "=" + id + "]");
                return result == null ? false : true;
            }
            return false;
        }

        /// <summary>
        /// Gets the compartment with the specified ID. Returns null if user doesn't exist.
        /// </summary>
        /// <param name="id">The ID to search for.</param>
        /// <returns>The XmlElement for that compartment or null if user doesn't exist.</returns>
        public XmlElement getCompartmentXmlById(int id)
        {
            return isFileLoaded ? getChildByAttribute(compartments, ID, id.ToString()) : null;
        }

        public Compartment getCompartmentById(int id)
        {
            return isFileLoaded ? new Compartment(getChildByAttribute(compartments, ID, id.ToString())) : null;
        }

        public ArrayList getCompartmentsWhere(String property, String value)
        {
            return this.getChildrenWhere("compartments", property, value);
        }

    #endregion

        #region Asset Functions

    /// <summary>
    /// Adds a new asset by name and compartment ID. Returns the asset's id if adding succeeded, 0 otherwise.
    /// </summary>
    /// <param name="name">The name of the asset.</param>
    /// <param name="compartmentID">The ID of the compartment.</param>
    /// <returns>True if adding succeeded, false otherwise. </returns>
    public int addAsset(string rfid, String name, int compartmentId, int userId)
    {
        if (isFileLoaded)
        {
            XmlElement newAsset = xmlDatabase.CreateElement(ASSET);

            newAsset.InnerXml += "<" + RFID + ">" + rfid + "</" + RFID + ">";
            newAsset.InnerXml += "<" + NAME + ">" + name + "</" + NAME + ">";
            newAsset.InnerXml += "<" + COMPID + ">" + compartmentId + "</" + COMPID + ">";
            newAsset.InnerXml += "<" + USERID + ">" + userId + "</" + USERID + ">";
            newAsset.SetAttribute(ID, getPrimaryKeyAndIncrement(assets));
            assets.AppendChild(newAsset);

            Console.WriteLine("Added new asset \"" + name + "\" (id = " + newAsset.GetAttribute(ID) +
                ") to compartment \"" + getCompartmentXmlById(compartmentId) + "\" (id = " + compartmentId + ")");
            return Convert.ToInt32(newAsset.GetAttribute(ID));
        }
        return 0;
    }

    /// <summary>
    /// Removes an asset by ID.
    /// 
    /// Returns true if removal succeeded, false otherwise. 
    /// </summary>
    /// <param name="assetId">The ID of the asset to remove.</param>
    /// <returns>True if removal succeeded, false otherwise. </returns>
    public Asset removeAssetById(int assetId)
    {
        if (isFileLoaded)
        {
            XmlNode assetToRemove = getChildByAttribute(assets, ID, assetId.ToString());

            if (assetToRemove != null)
            {
                Asset removedAsset = new Asset(assetToRemove);
                assets.RemoveChild(assetToRemove);
                Console.WriteLine("Removed asset \"" + assetToRemove[NAME].InnerText +
                    "\" (id = " + assetId + ")");
                return removedAsset;
            }
        }
        return null;
    }

        public Asset removeAssetByRfid(string rfid)
        {
            if (isFileLoaded)
            {
                XmlNode assetToRemove = assets.SelectSingleNode(ASSET + "[" + RFID + "=" + rfid + "]");
                if (assetToRemove != null)
                {
                    Asset removedAsset = new Asset(assetToRemove);
                    assets.RemoveChild(assetToRemove);
                    Console.WriteLine("Removed asset \"" + assetToRemove[NAME].InnerText +
                    "\" (id = " + assetToRemove[ID].InnerText + ")");
                    return removedAsset;
                }
            }
            return null;
        }

    /// <summary>
    /// Gets the asset with the specified ID. Returns null if asset doesn't exist.
    /// </summary>
    /// <param name="id">The ID to search for.</param>
    /// <returns>The XmlElement for that asset or null if log doesn't exist.</returns>
    public XmlElement getAssetXmlById(int id)
    {
        return isFileLoaded ? getChildByAttribute(assets, ID, id.ToString()) : null;
    }

    public Asset getAssetById(int id)
    {
        return isFileLoaded ? new Asset(getChildByAttribute(assets, ID, id.ToString())) : null;
    }

    public XmlElement getAssetXmlByRfid(string rfid)
    {
        if (isFileLoaded)
        {
            XmlNode match = assets.SelectSingleNode(ASSET + "[" + RFID + "=" + rfid + "]");
            return (match == null) ? (XmlElement)match : null;
        }
        return null;
    }

    public Asset getAssetByRfid(string rfid)
    {
        if (isFileLoaded)
        {
            XmlNode match = assets.SelectSingleNode(ASSET + "[" + RFID + "=" + rfid + "]");
            return (match == null) ? new Asset(match) : null;
        }
        return null;
    }

    /// <summary>
    /// Gets an XmlNodeList of assets with the specified compartment ID. Returns null if assets don't exist.
    /// </summary>
    /// <param name="compartmentId">The ID to search for.</param>
    /// <returns>The XmlNodeList of matching logs or null if logs don't exist.</returns>
    public XmlNodeList getAssetsXmlByCompartment(int compartmentId)
    {
        if (isFileLoaded)
        {
            XmlNodeList li = assets.SelectNodes(ASSET + "[" + COMPID + "=" + compartmentId + "]");
            return li.Count > 0 ? li : null;
        }

        return null;
    }

    public List<Asset> getAssetsByCompartment(int compartmentId)
    {
        if (isFileLoaded)
        {
            XmlNodeList li = assets.SelectNodes(ASSET + "[" + COMPID + "=" + compartmentId + "]");
            if (li.Count > 0)
            {
                List<Asset> assetList = new List<Asset>(li.Count);
                foreach (XmlNode xn in li)
                {
                    assetList.Add(new Asset(xn));
                }
                return assetList;
            }
        }
        return null;
    }

    /// <summary>
    /// Gets an XmlNodeList of assets with the specified borrower ID. Returns null if assets don't exist.
    /// </summary>
    /// <param name="userId">The ID to search for.</param>
    /// <returns>The XmlNodeList of matching assets or null if logs don't exist.</returns>
    public XmlNodeList getAssetsXmlByUser(int userId)
    {
        if (isFileLoaded)
        {
            XmlNodeList li = assets.SelectNodes(ASSET + "[" + USERID + "=" + userId + "]");
            return li.Count > 0 ? li : null;
        }
        return null;
    }

    public List<Asset> getAssetsByUser(int userId)
    {
        if (isFileLoaded)
        {
            XmlNodeList li = assets.SelectNodes(ASSET + "[" + USERID + "=" + userId + "]");
            if (li.Count > 0)
            {
                List<Asset> assetList = new List<Asset>();
                foreach (XmlNode xn in li)
                {
                    assetList.Add(xn);
                }
                return assetList;
            }
        }
        return null;
    }

    public ArrayList getAssetsWhere(String property, String value)
    {
        return this.getChildrenWhere("assets", property, value);
    }
    #endregion

        #region Event Functions
    /// <summary>
    /// Adds a new log.
    /// 
    /// Returns the event's id if adding succeeded, 0 otherwise.
    /// </summary>
    /// <param name="ownerId">The id of the event's associated owner.</param>
    /// <param name="compartmentId">The id of the compartment where the event took place.</param>
    /// <param name="eventType">The type of the event.</param>
    /// <param name="content">The content to add to the log.</param>
    /// <returns>True if adding succeeded, false otherwise. </returns>
    public int addEvent(int compartmentId, String eventType, int ownerId, int assetId, int deviceId, String content)
    {
        if (isFileLoaded)
        {
            XmlElement newEvent = xmlDatabase.CreateElement(EVENT);
            DateTime currentTime = DateTime.Now;

            newEvent.InnerXml += "<" + TIME + ">" + currentTime + "</" + TIME + ">";
            newEvent.InnerXml += "<" + COMPID + ">" + compartmentId + "</" + COMPID + ">";
            newEvent.InnerXml += "<" + TYPE + ">" + eventType + "</" + TYPE + ">";
            newEvent.InnerXml += "<" + USERID + ">" + ownerId + "</" + USERID + ">";
            newEvent.InnerXml += "<" + ASSETID + ">" + assetId + "</" + ASSETID + ">";
            newEvent.InnerXml += "<" + DEVICEID + ">" + deviceId + "</" + DEVICEID + ">";
            newEvent.InnerXml += "<" + CONTENT + ">" + content + "</" + CONTENT + ">";
            newEvent.SetAttribute(ID, getPrimaryKeyAndIncrement(logs));
            logs.AppendChild(newEvent);

            Console.WriteLine("Added new log (id = " + newEvent.GetAttribute(ID) + ")");
            return Convert.ToInt32(newEvent.GetAttribute(ID));
        }
        return 0;
    }

    /// <summary>
    /// Removes a log by ID.
    /// 
    /// Returns true if removal succeeded, false otherwise. 
    /// </summary>
    /// <param name="logId">The ID of the log to remove.</param>
    /// <returns>True if removal succeeded, false otherwise. </returns>
    public Event removeEvent(int logId)
    {
        if (isFileLoaded)
        {
            XmlNode eventToRemove = getChildByAttribute(logs, ID, logId.ToString());

            if (eventToRemove != null)
            {
                Event removedEvent = new Event(eventToRemove);

                logs.RemoveChild(eventToRemove);
                return removedEvent;
            }
        }
        return null;
    }

    /// <summary>
    /// Gets the log with the specified ID. Returns null if log doesn't exist.
    /// </summary>
    /// <param name="id">The ID to search for.</param>
    /// <returns>The XmlElement for that log or null if log doesn't exist.</returns>
    public XmlElement getEventXmlById(int id)
    {
        return isFileLoaded ? getChildByAttribute(logs, ID, id.ToString()) : null;
    }

    public Event getEventById(int id)
    {
        return isFileLoaded ? new Event(getChildByAttribute(logs, ID, id.ToString())) : null;
    }

    /// <summary>
    /// Gets an XmlNodeList of logs with the specified owner. Returns null if logs don't exist.
    /// </summary>
    /// <param name="ownerId">The ID to search for.</param>
    /// <returns>The XmlNodeList of matching logs or null if logs don't exist.</returns>
    public List<Event> getEventsByUser(int userId)
    {
        if (isFileLoaded)
        {
            XmlNodeList li = logs.SelectNodes(EVENT + "[" + USERID + "=" + userId + "]");

            if (li.Count > 0)
            {
                List<Event> eventList = new List<Event>(li.Count);

                foreach(XmlNode xn in li)
                {
                    eventList.Add(xn);
                }
                return eventList;
            }
        }

        return null;
    }

    /// <summary>
    /// Gets an XmlNodeList of logs with the specified compartment. Returns null if logs don't exist.
    /// </summary>
    /// <param name="compartmentId">The ID to search for.</param>
    /// <returns>The XmlNodeList of matching logs or null if logs don't exist.</returns>
    public List<Event> getEventsByCompartment(int compartmentId)
    {
        if (isFileLoaded)
        {
            XmlNodeList li = logs.SelectNodes(EVENT + "[" + COMPID+ "=" + compartmentId + "]");

            if (li.Count > 0)
            {
                List<Event> eventList = new List<Event>(li.Count);

                foreach (XmlNode xn in li)
                {
                    eventList.Add(xn);
                }
                return eventList;
            }
        }
        return null;
    }

    public ArrayList getEventsWhere(String property, String value)
    {
        return this.getChildrenWhere("logs", property, value);
    }

    #endregion

    }
}
